/*   TouchStone run platform is a software to run lab experiments. It is         *
 *   published under the terms of a BSD license (see details below)              *
 *   Author: Caroline Appert (appert@lri.fr)                                     *
 *   Copyright (c) 2010 Caroline Appert and INRIA, France.                       *
 *   TouchStone run platform reuses parts of an early version which were         *
 *   programmed by Jean-Daniel Fekete under the terms of a MIT (X11) Software    *
 *   License (Copyright (C) 2006 Jean-Daniel Fekete and INRIA, France)           *
 *********************************************************************************/
/* Redistribution and use in source and binary forms, with or without            * 
 * modification, are permitted provided that the following conditions are met:   *

 *  - Redistributions of source code must retain the above copyright notice,     *
 *    this list of conditions and the following disclaimer.                      *
 *  - Redistributions in binary form must reproduce the above copyright notice,  *
 *    this list of conditions and the following disclaimer in the documentation  *
 *    and/or other materials provided with the distribution.                     *
 *  - Neither the name of the INRIA nor the names of its contributors   *
 * may be used to endorse or promote products derived from this software without *
 * specific prior written permission.                                            *

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"   *
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     *
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE    *
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE     *
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR           *
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF          *
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS      *
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN       *
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)       *
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE    *
 * POSSIBILITY OF SUCH DAMAGE.                                                   *
 *********************************************************************************/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Properties;
import java.util.TreeMap;

import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.RootDoc;
import com.sun.javadoc.Tag;
import com.sun.javadoc.Type;

/**
 * <b>FactoryPropertiesGenerator</b> generate Factory resource files
 * from javadoc annotations.
 * 
 */
public class FactoryPropertiesGenerator {
	static TreeMap factories = new TreeMap();

	/**
	 * Starts the javadoc processing.
	 * @param root the root doc element
	 * @return true if the processing went well
	 */
	public static boolean start(RootDoc root) {
		//		manageAxesContents(root.classes());
		(new File("src/resources/")).mkdirs();
		manageMeasures(root.classes());
		manageFactoryContents(root.classes());
		generateProperties();
		try {
			factorsDescription = new PrintWriter(new File("src/resources/factors.description"));
			valuesDescription = new PrintWriter(new File("src/resources/values.description"));
			valuesDescription.println("# This file is automatically generated DO NOT MODIFY!");
		} catch(Exception e) {
			e.printStackTrace();
		}
		manageFactorsAndValues(root.classes());
		criteria(root.classes());
		blocksAndIntertitles(root.classes());
		return true;
	}

	private static void manageFactoryContents(ClassDoc[] classes) {
		String tagName = "touchstone.factory";
		for (int i = 0; i < classes.length; i++) {
			Tag[] tags = classes[i].tags(tagName);
			if (tags.length > 0) {
				for (int k = 0; k < tags.length; k++) {
					String text = tags[k].text();
					String[] params = parseLine(text);
					if (params.length == 0) {
						System.err
						.println("Not enough arguments in @touchstone.factory tag for "
								+ classes[i].qualifiedName());
						continue;
					}
					String factoryName = params[0];

					TreeMap factoryMap = (TreeMap) factories.get(factoryName);
					if (factoryMap == null) {
						factoryMap = new TreeMap();
						factories.put(factoryName, factoryMap);
					}
					params[0] = classes[i].qualifiedName();
					// 0=class name, 1=name, 2... extra params
					if (params.length > 1) {
						factoryMap.put(params[1], params);
					}
				}
			}
		}
	}

	private static Properties getProperties(String docLine, String[] propertyNames) {
		String[] parts = parseLine(docLine);
		for (int i = 0; i < parts.length; i++) {
			parts[i] = parts[i].trim();
		}
		Properties p = new Properties();
		p.put("id", parts[0]);
		for(int cpt = 0; cpt < propertyNames.length; cpt++) {
			for(int i = 1; i < parts.length; i++) {
				String next = parts[i];
				if(next.equalsIgnoreCase(propertyNames[cpt])) {
					p.put(propertyNames[cpt], parts[i+1]);
					break;
				}
			}
		}
		return p;
	}



	// I cannot succeed in using in a doclet:
	// - neither java reflection (to replace doc tags by static methods)
	// - nor include jdom (to generate xml while generating properties files) => that's why I use the description files as intermediary

	private static PrintWriter factorsDescription;
	private static PrintWriter valuesDescription;

	private static String getFactorType(Type type) {
		if(type == null) return "character";
		if(type.qualifiedTypeName().compareTo("fr.inria.insitu.touchstone.run.CharacterFactor") == 0)
			return "character";
		else
			if(type.qualifiedTypeName().compareTo("fr.inria.insitu.touchstone.run.NumericalFactor") == 0)
				return "number";
			else {
				ClassDoc cd = type.asClassDoc();
				return getFactorType(cd.superclassType());
			}
	}

	private static void descriptionFiles(ClassDoc[] classes) {
		try {
			for(int i = 0; i < classes.length; i++) {
				Tag[] tags;
				tags = classes[i].tags("touchstone.factor");
				for (int k = 0; k < tags.length; k++) {
					String text = tags[k].text();
					String[] propertyNames = {"name","help"};
					Properties props = getProperties(text, propertyNames);
					if(props != null) {
						factorsDescription.write("id:"+props.getProperty("id")+"\n");
						factorsDescription.write("name:"+props.getProperty("name")+"\n");
						factorsDescription.write("help:"+props.getProperty("help")+"\n");
						factorsDescription.write("type:"+getFactorType(classes[i])+"\n");
					}
				}
				tags = classes[i].tags("touchstone.value");
				for (int k = 0; k < tags.length; k++) {
					String text = tags[k].text();
					String[] propertyNames = {"factor","name","help"};
					Properties props = getProperties(text, propertyNames);
					if(props != null) {
						valuesDescription.println("factor:"+props.getProperty("factor"));
						valuesDescription.println("id:"+props.getProperty("id"));
						valuesDescription.println("name:"+props.getProperty("name"));
						valuesDescription.println("help:"+props.getProperty("help"));
						valuesDescription.println();
						//						System.out.println("id: "+props.getProperty("id"));
						//						System.out.println("factor: "+props.getProperty("factor"));
						//						System.out.println("name: "+props.getProperty("name"));
						//						System.out.println("help: "+props.getProperty("help"));
					}
				}
			}
			factorsDescription.close();
			valuesDescription.close();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

	private static void manageFactorsAndValues(ClassDoc[] classes) {
		descriptionFiles(classes);
		try {
			PrintWriter pfout = new PrintWriter(new File("src/resources/factoriesForFactors.properties"));

			pfout.println("# This file is automatically generated DO NOT MODIFY!");

			for (int i = 0; i < classes.length; i++) {
				Tag[] tags = classes[i].tags("touchstone.factor");
				for (int k = 0; k < tags.length; k++) {
					String text = tags[k].text();
					String[] parts = parseLine(text);
					pfout.println(classes[i].qualifiedName()+":"+parts[0]);
				}
			}

			pfout.flush();
			pfout.close();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		try {
			PrintWriter pfout = new PrintWriter(new File("src/resources/values.properties"));
			int cpt = 0;
			for (int i = 0; i < classes.length; i++) {
				Tag[] tags = classes[i].tags("touchstone.value");
				if (tags.length > 0) {
					for (int k = 0; k < tags.length; k++) {
						String text = tags[k].text();
						String[] propertyNames = {"factor","name","help"};
						Properties props = getProperties(text, propertyNames);
						if(props == null) {
							//TODO print the correct message
							System.err.println("ERROR!!! "+classes[i].qualifiedName());
						}
						pfout.println("factor."+cpt+":"+props.getProperty("factor"));
						pfout.println("name."+cpt+":"+props.getProperty("id"));
						pfout.println("class."+cpt+":"+classes[i].qualifiedName());
						pfout.println();
						cpt++;
						if(props.getProperty("name") != null) {
							pfout.println("factor."+cpt+":"+props.getProperty("factor"));
							pfout.println("name."+cpt+":"+props.getProperty("name"));
							pfout.println("class."+cpt+":"+classes[i].qualifiedName());
							pfout.println();
							cpt++;
						}
					}
				}
			}
			pfout.flush();
			pfout.close();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	private static void generateProperties() {
		for (Iterator fiter = factories.keySet().iterator(); fiter.hasNext();) {
			String factoryName = (String) fiter.next();
			String propName = factoryName.toLowerCase() + ".properties";
			TreeMap factoryMap = (TreeMap) factories.get(factoryName);
			try {
				int index = 0;
				FileWriter rout = new FileWriter("src/resources/" + propName);
				BufferedWriter out = new BufferedWriter(rout);
				PrintWriter pout = new PrintWriter(out);
				pout
				.println("# This file is automatically generated DO NOT MODIFY!");

				for (Iterator iter = factoryMap.keySet().iterator(); iter
				.hasNext();) {
					String name = (String) iter.next();
					String[] params = (String[]) factoryMap.get(name);
					pout.println("name." + index + ":" + name);
					pout.println("class." + index + ":" + params[0]);
					for (int i = 2; i < params.length; i++) {
						pout.println("data" + (i - 1) + "." + index + ":"
								+ params[i]);
					}
					pout.println();
					index++;
				}
				pout.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
	}

	private static String[] parseLine(String text) {
		StringReader sin = new StringReader(text);
		BufferedReader br = new BufferedReader(sin);
		ArrayList<String> list = new ArrayList<String>();
		String line;
		try {
			line = br.readLine();
			while(line != null) {
				String[] parts = line.split(":");
				for (int j = 0; j < parts.length; j++) {
					String removeBlanksAndQuotes = parts[j].trim();
					if(removeBlanksAndQuotes.length() == 0)
						parts[j] = "null";
					else if(removeBlanksAndQuotes.charAt(0) == '"' 
						&& removeBlanksAndQuotes.charAt(removeBlanksAndQuotes.length()-1) == '"')
						parts[j] = removeBlanksAndQuotes.substring(1, removeBlanksAndQuotes.length()-1);
					list.add(parts[j]);					
				}
				line = br.readLine();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		String[] params = new String[list.size()];
		list.toArray(params);
		return params;
	}

	private static ArrayList<ClassDoc> getAllInterfaces(ArrayList<ClassDoc> res, ClassDoc cdoc) {
		ClassDoc[] interfaces = cdoc.interfaces();
		for(int i = 0; i < interfaces.length; i++) {
			if(res.contains(interfaces[i])) res.add(interfaces[i]);
			getAllInterfaces(res, interfaces[i]);
		}
		return res;
	}

	// TODO FIX if used
	private static ArrayList<ClassDoc> getHierarchy(ClassDoc cdoc) {
		// it is an interface
		ArrayList<ClassDoc> res = new ArrayList<ClassDoc>();
		if(cdoc.superclass() == null) {
			return getAllInterfaces(res, cdoc);
		} else {
			// it is a "root" class (i.e. subclass of Object)
			if(cdoc.superclass().qualifiedName().compareTo("java.lang.Object") == 0) {
				return getAllInterfaces(res, cdoc);
			} else {
				getAllInterfaces(res, cdoc);
				if(!res.contains(cdoc.superclass())) res.add(cdoc.superclass());
				ArrayList<ClassDoc> parentHierarchy = getHierarchy(cdoc.superclass());
				for(Iterator<ClassDoc> i = parentHierarchy.iterator(); i.hasNext(); ) {
					ClassDoc next = i.next();
					if(!res.contains(next)) res.add(next);
				}
				return res;
			}
		}
	}

	private static boolean isSubClass(ClassDoc mother, ClassDoc daughter) {
		ArrayList<ClassDoc> hierarchy = getHierarchy(daughter);
		return hierarchy.contains(mother);
	}

	private static void manageAxesContents(ClassDoc[] classes) {
		String tagName = "touchstone.axes";
		PrintWriter pout = null;
		try {
			pout = new PrintWriter(new File("src/resources/axes.properties"));
			pout.println("# This file is automatically generated DO NOT MODIFY!");

			for (int i = 0; i < classes.length; i++) {
				Tag[] tags = classes[i].tags(tagName);
				boolean done = (tags.length > 0); 
				for (int k = 0; k < tags.length; k++) {
					done = true;
					String text = tags[k].text();
					pout.print(classes[i].qualifiedName()+": "+text);
				}
				for(int l = 0; l < classes.length; l++) {
					Tag[] tgs = classes[l].tags(tagName);
					if(tgs.length > 0) {
						if((classes[l] != classes[i]) && isSubClass(classes[l], classes[i])) {
							for (int k = 0; k < tgs.length; k++) {
								String text = tgs[k].text();
								if (!done) {
									done = true;
									pout.print(classes[i].qualifiedName()+":");
								}
								pout.print(" "+text);
							}
						}
					}
				}
				if(done) pout.println();
			}

			pout.flush();
			pout.close();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	private static String parseFactor(String factor) {
		factor = factor.replaceAll("[\n\r \t]+", " ");
		return factor;
	}

	private static void manageMeasures(ClassDoc[] classes) {
		// measuresExported.properties which measures are exported by which
		// component of the run platform. When a class or an interface declares itself
		// as a measure exporter (it contains a @touchstone.measure tag), each of its
		// sublasses are also marked as measures exporters.
		// measures.description describes each known measure to generate the xml file
		// used to fill the menus of the design platform.
		// measures.description is a file containing a list of measures descriptions.
		// Each measure description is formatted like that:
		// id:<measure_id>
		// name:<measure_name>
		// help:<measure_help>
		// type:<measure_type>
		try {
			PrintWriter pfoutProperties = new PrintWriter(new File("src/resources/measuresExported.properties"));
			pfoutProperties.println("# This file is automatically generated DO NOT MODIFY!");
			PrintWriter pfoutDescription = new PrintWriter(new File("src/resources/measures.description"));
			pfoutDescription.println("# This file is automatically generated DO NOT MODIFY!");

			for (int i = 0; i < classes.length; i++) {
				Tag[] tags = classes[i].tags("touchstone.measure");
				for (int k = 0; k < tags.length; k++) {
					String text = tags[k].text();
					String[] propertyNames = {"name", "help", "type" };
					Properties p = getProperties(text, propertyNames);
					if(p == null) {
						System.err.println(
								"In class "+classes[i].qualifiedName()+":\n" +
								"\tthe tag  @touchstone.measure is not properly written.\n" +
								"\tIt should look like: \n" +
								"\t@touchstone.measure <measure_id>\n" +
								"\t\tid: <measure_id>\n" +
								"\t\tname: <measure_name>\n" +
								"\t\thelp: <measure_help>\n" +
								"For now, it looks like: \n" +
								"\t@touchstone.measure "+text);
					} else {
						pfoutDescription.println("id:"+p.getProperty("id"));
						for(int j = 0; j < propertyNames.length; j++) {
							pfoutDescription.println(propertyNames[j]+":"+p.getProperty(propertyNames[j]));
						}
						pfoutDescription.println();
					}
					// also marks the daughter classes as exporters
					pfoutProperties.println(classes[i].qualifiedName()+":"+p.getProperty("id"));
					for(int l = 0; l < classes.length; l++) {
						if((classes[l] != classes[i]) && isSubClass(classes[i], classes[l])) {
							pfoutProperties.println(classes[l].qualifiedName()+":"+p.getProperty("id"));
						}
					}
				}
			}

			pfoutProperties.flush();
			pfoutProperties.close();
			pfoutDescription.flush();
			pfoutDescription.close();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	private static void criteria(ClassDoc[] classes) {
		try {
			PrintWriter pfout = new PrintWriter(new File("src/resources/criteria.properties"));
			int cpt = 0;
			for (int i = 0; i < classes.length; i++) {
				Tag[] tags = classes[i].tags("touchstone.criterion");
				if (tags.length > 0) {
					for (int k = 0; k < tags.length; k++) {
						String text = tags[k].text();
						text = parseFactor(text);
						String[] parts = parseLine(text);
						pfout.println("name."+cpt+":"+parts[0]);
						pfout.println("class."+cpt+":"+classes[i].qualifiedName());
						pfout.println();
						cpt++;
					}
				}
			}
			pfout.flush();
			pfout.close();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	private static void blocksAndIntertitles(ClassDoc[] classes) {
		try {
			PrintWriter pfout = new PrintWriter(new File("src/resources/intertitles.properties"));
			int cpt = 0;
			for (int i = 0; i < classes.length; i++) {
				Tag[] tags = classes[i].tags("touchstone.intertitle");
				if (tags.length > 0) {
					for (int k = 0; k < tags.length; k++) {
						String text = tags[k].text();
						text = parseFactor(text);
						String[] parts = parseLine(text);
						pfout.println("name."+cpt+":"+parts[0]);
						pfout.println("class."+cpt+":"+classes[i].qualifiedName());
						pfout.println();
						cpt++;
					}
				}
			}
			pfout.flush();
			pfout.close();
			pfout = new PrintWriter(new File("src/resources/blocks.properties"));
			cpt = 0;
			for (int i = 0; i < classes.length; i++) {
				Tag[] tags = classes[i].tags("touchstone.block");
				if (tags.length > 0) {
					for (int k = 0; k < tags.length; k++) {
						String text = tags[k].text();
						text = parseFactor(text);
						String[] parts = parseLine(text);
						pfout.println("name."+cpt+":"+parts[0]);
						pfout.println("class."+cpt+":"+classes[i].qualifiedName());
						pfout.println();
						cpt++;
					}
				}
			}
			pfout.flush();
			pfout.close();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

}